/*
 * Copyright 2009 Geoffrey Chandler
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.stuffedit.search;

import org.apache.log4j.Logger;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.*;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.ArrayList;

import com.sun.tools.javac.util.Context;

/**
 * Description
 * <p/>
 * <small>
 * Submitted by geoffc on Mar 15, 2009 at 10:02:46 PM
 * </small>
 *
 * @author Geoffrey Chandler, geoffc@gmail.com
 */
public class GenericSearchIndexLucene<T> implements GenericSearchIndex<T> {
    protected Logger log = Logger.getLogger(GenericSearchIndexLucene.class);

    private Analyzer analyzer;
    private Integer maxFieldLength;
    private String indexPath;
    private Context.Factory<T> factory;

    public GenericSearchIndexLucene(String indexPath) throws IOException {
        this.setAnalyzer(new StandardAnalyzer());
        this.setMaxFieldLength(10000);
        this.indexPath = indexPath;
    }

    public void setFactory(Context.Factory<T> factory) {
        this.factory = factory;
    }

    public void add(T document) throws IOException {
        Document d = new Document();
        Class clazz = document.getClass();
        while (clazz != null) {
            for (java.lang.reflect.Field f : clazz.getDeclaredFields()) {
                Annotation fieldAnnotation = f.getAnnotation(SearchField.class);
                if (fieldAnnotation instanceof SearchField) {
                    SearchField searchField = (SearchField) fieldAnnotation;
                    try {
                        f.setAccessible(true);
                        d.add(new Field(
                                searchField.field().equals("")
                                        ? f.getName()
                                        : searchField.field(),
                                f.get(document).toString(),
                                searchField.stored()
                                        ? Field.Store.YES
                                        : Field.Store.NO,
                                searchField.analyzed()
                                        ? Field.Index.ANALYZED
                                        : Field.Index.NOT_ANALYZED));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
        this.write(d);
    }

    private void write(Document d) throws IOException {
        IndexWriter writer = this.getWriter();
        writer.addDocument(d);
        writer.optimize();
        writer.close();
    }

    private IndexWriter getWriter() throws IOException {
        File index = new File(indexPath);
        return new IndexWriter(
                index,
                this.analyzer,
                !index.exists(),
                new IndexWriter.MaxFieldLength(this.maxFieldLength));
    }

    public List<SearchResult<T>> searchField(final String field,
                                             final String queryString,
                                             final int start,
                                             final int hitsPerPage)
            throws IOException {

        IndexReader reader = IndexReader.open(indexPath);
        Searcher searcher = new IndexSearcher(reader);
        TopDocCollector collector = new TopDocCollector(hitsPerPage);

        QueryParser parser = new QueryParser(field, analyzer);
        Query query = null;
        try {
            query = parser.parse(queryString);
        } catch (ParseException e) {
            e.printStackTrace();

            // do something more helpful here
        }

        searcher.search(query, collector);
        ScoreDoc[] hits = collector.topDocs().scoreDocs;

        List<SearchResult<T>> results = new ArrayList<SearchResult<T>>();
        for (int i = start; i < Math.min(hits.length,
                start + hitsPerPage); i++) {
            int docId = hits[i].doc;
            Document d = searcher.doc(docId);
            T instance = factory.make();
            Class clazz = instance.getClass();
            while (clazz != null) {
                for (java.lang.reflect.Field f : clazz.getDeclaredFields()) {
                    Annotation fieldAnnotation = f.getAnnotation(SearchField.class);
                    if (fieldAnnotation instanceof SearchField) {
                        SearchField searchField = (SearchField) fieldAnnotation;
                        try {
                            f.setAccessible(true);
                            f.set(instance,
                                    d.getField(searchField.field().equals("")
                                            ? f.getName()
                                            : searchField.field()
                                    ).stringValue());
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
                clazz = clazz.getSuperclass();
            }
            results.add(new SearchResult<T>(instance, d.getBoost()));
        }
        return results;
    }

    public List<SearchResult<T>> search(final String queryString,
                                        final int start,
                                        final int hitsPerPage)
            throws IOException {


        return null;
    }

    public void setAnalyzer(Analyzer analyzer) {
        this.analyzer = analyzer;
    }

    public void setMaxFieldLength(Integer maxFieldLength) {
        this.maxFieldLength = maxFieldLength;
    }

}
